<template>
	<div class="beat-time-container">
		<div class="container">
			<span class="title">节拍器</span>
			
			<div class="controls">
				<div class="control-item">
					<span>速度 (BPM):</span>
					<input 
						type="number" 
						v-model.number="bpm" 
						@change="updateBPM"
						:min="30" 
						:max="240" 
						class="bpm-input"
					/>
				</div>
			</div>
			
			<div class="controls">
				<div class="control-item">
					<span>拍号:</span>
					<select 
						:value="timeSignatureIndex" 
						@change="onTimeSignatureChange"
						class="time-signature-picker"
					>
						<option 
							v-for="(option, index) in timeSignatureOptions" 
							:key="index" 
							:value="index"
						>
							{{ option }}
						</option>
					</select>
				</div>
			</div>
			
			<div class="controls">
				<button 
					@click="togglePlay" 
					:class="['control-button', isPlaying ? 'stop' : 'start']"
				>
					{{ isPlaying ? '停止' : '开始' }}
				</button>
				<button 
					@click="generateExpectedTimes" 
					class="control-button"
				>
					生成时间
				</button>
				<button 
					@click="handleTap" 
					@touchstart="handleTap"
					class="tap-button"
				>
					打拍子
				</button>
			</div>
			
			<div id="paper" class="paper"></div>
			<div id="score" class="score"></div>
			
			<div v-if="showExpectedTimes" class="expected-times">
				<span class="section-title">预计每拍时间（从0开始）：</span>
				<div class="time-header">
					<span>拍号位置</span>
					<span>预计时间</span>
					<span>点击检测时间段</span>
				</div>
				<div class="time-list" :style="{ maxHeight: '300px', overflowY: 'auto' }">
					<div 
						v-for="(time, index) in expectedTimesList" 
						:key="index"
						:class="['time-entry', { 'strong-beat': time.isStrongBeat }]"
					>
						<span class="beat-position">{{ time.position }}</span>
						<span class="expected-time">{{ time.time }}</span>
						<span class="detection-range">{{ time.range }}</span>
					</div>
				</div>
			</div>
			
			<div class="beat-log">
				<div class="log-list" :style="{ maxHeight: '300px', overflowY: 'auto' }">
					<div 
						v-for="(log, index) in beatLogs" 
						:key="index"
						:class="['log-entry', log.type, { 'first-beat': log.isFirstBeat }]"
					>
						<span class="time">{{ log.time }}</span>
						<span class="content"> - {{ log.content }}</span>
					</div>
				</div>
			</div>
		</div>
	</div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

// 响应式数据
const bpm = ref(120)
const timeSignature = ref('4/4')
const timeSignatureIndex = ref(0)
const isPlaying = ref(false)
const currentBeat = ref(0)
const startTime = ref(null)
const intervalId = ref(null)
const checkMissedBeatsInterval = ref(null)
const audioContext = ref(null)
const strongBeatSynth = ref(null)
const weakBeatSynth = ref(null)
const lastCheckedBeatIndex = ref(-1)
const showExpectedTimes = ref(false)
const expectedTimesList = ref([])
const beatLogs = ref([])
const scrollTop = ref(0)
const debugLogs = ref([])
const timeSignatureOptions = ref([
	'4/4', '3/4', '2/4', '2/2',
	'6/8', '9/8', '12/8',
	'5/4', '7/8', '3/8', '6/4'
])

onMounted(() => {
	initMetronome()
})

onBeforeUnmount(() => {
	cleanup()
})

// 方法函数
const initMetronome = async () => {
	try {
		await initABCJS()
		console.log('节拍器初始化成功')
	} catch (error) {
		console.error('节拍器初始化失败:', error)
	}
}

const initABCJS = async () => {
	try {
		audioContext.value = new (window.AudioContext || window.webkitAudioContext)()
		console.log('音频上下文初始化成功')
	} catch (error) {
		console.error('音频上下文初始化失败:', error)
	}
}

const togglePlay = () => {
	if (isPlaying.value) {
		stop()
	} else {
		start()
	}
}

const start = () => {
	isPlaying.value = true
	startTime.value = performance.now()
	playBeat()
	const interval = (60 / bpm.value) * 1000
	intervalId.value = setInterval(() => playBeat(), interval)
	const checkInterval = (60 / bpm.value) * 250
	lastCheckedBeatIndex.value = -1
	checkMissedBeatsInterval.value = setInterval(() => checkMissedBeats(), checkInterval)
}

const stop = () => {
	isPlaying.value = false
	currentBeat.value = 0
	startTime.value = null
	if (intervalId.value) {
		clearInterval(intervalId.value)
		intervalId.value = null
	}
	if (checkMissedBeatsInterval.value) {
		clearInterval(checkMissedBeatsInterval.value)
		checkMissedBeatsInterval.value = null
	}
	lastCheckedBeatIndex.value = -1
	debugLogs.value = []
}

const playBeat = async () => {
	const [numerator, denominator] = timeSignature.value.split('/').map(Number)
	const elapsedTime = (performance.now() - startTime.value) / 1000
	const timeString = formatTime(elapsedTime)
	const isStrongBeat = currentBeat.value === 0 || 
		(denominator === 8 && (numerator === 6 || numerator === 9 || numerator === 12) && currentBeat.value % 3 === 0) ||
		(numerator === 4 && currentBeat.value === 2) ||
		(numerator === 5 && currentBeat.value === 3) ||
		(numerator === 6 && currentBeat.value === 3)

	const currentBar = Math.floor(currentBeat.value / numerator) + 1
	const currentBeatInBar = (currentBeat.value % numerator) + 1
	const beatType = isStrongBeat ? '强拍' : '弱拍'
	
	logBeat(timeString, currentBar, currentBeatInBar, beatType)
	currentBeat.value = (currentBeat.value + 1) % numerator
}

const updateBPM = () => {
	showExpectedTimes.value = false
	expectedTimesList.value = []
	if (isPlaying.value) {
		stop()
		start()
	}
}

const onTimeSignatureChange = (e) => {
	timeSignatureIndex.value = parseInt(e.target.value)
	timeSignature.value = timeSignatureOptions.value[timeSignatureIndex.value]
	currentBeat.value = 0
	showExpectedTimes.value = false
	expectedTimesList.value = []
}

const handleTap = () => {
	if (isPlaying.value) {
		const tapTime = (performance.now() - startTime.value) / 1000
		logTap(tapTime)
		checkTapTiming(tapTime)
	}
}

const cleanup = () => {
	stop()
	if (audioContext.value) {
		audioContext.value.close()
	}
}

const generateExpectedTimes = () => {
	const [numerator] = timeSignature.value.split('/').map(Number)
	const beatInterval = 60 / bpm.value
	const halfBeatInterval = beatInterval / 2
	expectedTimesList.value = []
	
	for (let i = 0; i < 100; i++) {
		const totalSeconds = i * beatInterval
		const detectionStart = totalSeconds - halfBeatInterval
		const detectionEnd = totalSeconds + halfBeatInterval
		const timeString = formatTime(totalSeconds)
		const startTimeString = formatTime(Math.max(0, detectionStart))
		const endTimeString = formatTime(detectionEnd)
		const currentBar = Math.floor(i / numerator) + 1
		const currentBeat = (i % numerator) + 1
		
		expectedTimesList.value.push({
			position: `第${currentBar}小节第${currentBeat}拍`,
			time: timeString,
			range: `${startTimeString} ~ ${endTimeString}`,
			isStrongBeat: currentBeat === 1
		})
	}
	showExpectedTimes.value = true
}

const logBeat = (time, bar, beat, beatType) => {
	const log = {
		time: time,
		content: `第${bar}小节第${beat}拍 (${beatType})`,
		type: 'beat',
		isFirstBeat: beat === 1
	}
	beatLogs.value.unshift(log)
	if (beatLogs.value.length > 50) {
		beatLogs.value = beatLogs.value.slice(0, 50)
	}
	scrollTop.value = 0
}

const logTap = (elapsedTime) => {
	const timeString = formatTime(elapsedTime)
	const log = {
		time: timeString,
		content: '用户点击',
		type: 'tap'
	}
	beatLogs.value.unshift(log)
	if (beatLogs.value.length > 50) {
		beatLogs.value = beatLogs.value.slice(0, 50)
	}
	scrollTop.value = 0
}

const checkTapTiming = (tapTime) => {
	const [numerator] = timeSignature.value.split('/').map(Number)
	const beatInterval = 60 / bpm.value
	const nearestBeatIndex = Math.round(tapTime / beatInterval)
	const nearestBeatTime = nearestBeatIndex * beatInterval
	
	if (isCheckedBeat(nearestBeatIndex)) {
		return
	}

	const deviation = Math.round((tapTime - nearestBeatTime) * 1000)
	const absDeviation = Math.abs(deviation)
	const currentBar = Math.floor(nearestBeatIndex / numerator) + 1
	const currentBeat = (nearestBeatIndex % numerator) + 1
	const beatDuration = beatInterval * 1000
	const deviationPercentage = (absDeviation / beatDuration) * 100
	const isInValidRange = deviationPercentage <= 50

	if (!isInValidRange) {
		const log = {
			time: formatTime(tapTime),
			content: `第${currentBar}小节第${currentBeat}拍 (超出检测范围: ${deviation > 0 ? '延迟' : '提前'}${absDeviation}ms, ${deviationPercentage.toFixed(1)}%) Miss`,
			type: 'tap-result tap-miss'
		}
		beatLogs.value.unshift(log)
		return
	}
	
	let rating, ratingClass
	if (deviationPercentage <= 8) {
		rating = 'Perfect'
		ratingClass = 'tap-result tap-perfect'
	} else if (deviationPercentage <= 12) {
		rating = 'Great'
		ratingClass = 'tap-result tap-great'
	} else if (deviationPercentage <= 16) {
		rating = 'Good'
		ratingClass = 'tap-result tap-good'
	} else {
		rating = 'Miss'
		ratingClass = 'tap-result tap-miss'
	}
	
	const log = {
		time: formatTime(tapTime),
		content: `第${currentBar}小节第${currentBeat}拍 (${deviation > 0 ? '延迟' : '提前'}${absDeviation}ms, ${deviationPercentage.toFixed(1)}%) ${rating}`,
		type: ratingClass
	}
	
	beatLogs.value.unshift(log)
	lastCheckedBeatIndex.value = Math.max(lastCheckedBeatIndex.value, nearestBeatIndex)
}

const checkMissedBeats = () => {
	if (!isPlaying.value) return
	const currentTime = (performance.now() - startTime.value) / 1000
	const beatInterval = 60 / bpm.value
	const currentBeatIndex = Math.floor(currentTime / beatInterval)

	for (let i = lastCheckedBeatIndex.value + 1; i <= currentBeatIndex - 1; i++) {
		const beatTime = i * beatInterval
		const nextBeatTime = (i + 1) * beatInterval
		if (currentTime >= nextBeatTime) {
			if (!isCheckedBeat(i)) {
				logMissedBeat(i)
				lastCheckedBeatIndex.value = i
			}
		}
	}
}

const logMissedBeat = (beatIndex) => {
	const [numerator] = timeSignature.value.split('/').map(Number)
	const beatTime = beatIndex * (60 / bpm.value)
	const currentBar = Math.floor(beatIndex / numerator) + 1
	const currentBeat = (beatIndex % numerator) + 1
	const log = {
		time: formatTime(beatTime),
		content: `第${currentBar}小节第${currentBeat}拍 (Miss)`,
		type: 'tap-result tap-miss'
	}
	beatLogs.value.unshift(log)
}

const isCheckedBeat = (beatIndex) => {
	return beatIndex <= lastCheckedBeatIndex.value
}

const formatTime = (seconds) => {
	const minutes = Math.floor(seconds / 60)
	const remainingSeconds = seconds % 60
	const milliseconds = Math.round((remainingSeconds % 1) * 1000)
	return `${String(minutes).padStart(2, '0')}:${String(Math.floor(remainingSeconds)).padStart(2, '0')}.${String(milliseconds).padStart(3, '0')}`
}
</script>

<style scoped>
	.beat-time-container {
		padding: 20px;
		background-color: #f5f5f5;
		min-height: 100vh;
	}

	.container {
		max-width: 600px;
		margin: 0 auto;
		background-color: white;
		border-radius: 10px;
		padding: 20px;
		box-shadow: 0 2px 10px rgba(0,0,0,0.1);
	}

	.title {
		font-size: 24px;
		font-weight: bold;
		text-align: center;
		color: #333;
		margin-bottom: 20px;
		display: block;
	}

	.controls {
		margin: 20px 0;
		display: flex;
		flex-direction: column;
		align-items: center;
	}

	.control-item {
		display: flex;
		align-items: center;
		justify-content: center;
		margin: 10px 0;
	}

	.control-item text {
		margin-right: 10px;
		font-size: 16px;
		color: #666;
	}

	.bpm-input {
		padding: 8px 12px;
		border: 1px solid #ddd;
		border-radius: 4px;
		font-size: 16px;
		width: 100px;
		text-align: center;
	}

	.time-signature-picker {
		padding: 8px 12px;
		border: 1px solid #ddd;
		border-radius: 4px;
		background-color: white;
		min-width: 80px;
		text-align: center;
	}

	.control-button {
		padding: 12px 24px;
		margin: 5px;
		font-size: 16px;
		border: none;
		border-radius: 6px;
		background-color: #2196F3;
		color: white;
		cursor: pointer;
		transition: background-color 0.3s;
	}

	.control-button:hover {
		background-color: #1976D2;
	}

	.control-button.stop {
		background-color: #f44336;
	}

	.control-button.stop:hover {
		background-color: #d32f2f;
	}

	.tap-button {
		padding: 12px 24px;
		margin: 5px;
		font-size: 16px;
		border: none;
		border-radius: 6px;
		background-color: #4CAF50;
		color: white;
		cursor: pointer;
		transition: all 0.2s;
		user-select: none;
	}

	.tap-button:active {
		background-color: #45a049;
		transform: scale(0.95);
	}

	.paper, .score {
		margin: 20px 0;
		padding: 15px;
		background-color: #fff;
		border-radius: 8px;
		box-shadow: 0 2px 5px rgba(0,0,0,0.1);
		overflow-x: auto;
	}

	.expected-times {
		margin-top: 20px;
		padding: 15px;
		background-color: #f0f8ff;
		border-radius: 8px;
		box-shadow: 0 2px 5px rgba(0,0,0,0.1);
	}

	.section-title {
		font-size: 18px;
		font-weight: bold;
		color: #333;
		margin-bottom: 15px;
		display: block;
	}

	.time-header {
		display: grid;
		grid-template-columns: 1fr 1fr 2fr;
		padding: 10px 5px;
		font-weight: bold;
		border-bottom: 2px solid #2196F3;
		margin-bottom: 10px;
		font-size: 14px;
		color: #333;
	}

	.time-list {
		max-height: 300px;
	}

	.time-entry {
		display: grid;
		grid-template-columns: 1fr 1fr 2fr;
		padding: 8px 5px;
		border-bottom: 1px solid #e0e0e0;
		font-size: 14px;
		align-items: center;
	}

	.time-entry.strong-beat {
		color: #2196F3;
		font-weight: bold;
	}

	.beat-position {
		text-align: left;
	}

	.expected-time {
		text-align: center;
	}

	.detection-range {
		text-align: center;
		color: #666;
	}

	.beat-log {
		margin-top: 20px;
		padding: 15px;
		background-color: #f5f5f5;
		border-radius: 8px;
		box-shadow: 0 2px 5px rgba(0,0,0,0.1);
	}

	.log-list {
		max-height: 300px;
	}

	.log-entry {
		padding: 8px;
		border-bottom: 1px solid #ddd;
		font-size: 14px;
		font-family: monospace;
	}

	.log-entry.first-beat {
		color: #2196F3;
		font-weight: bold;
	}

	.log-entry.tap {
		background-color: #f0f0f0;
	}

	.log-entry.tap-result {
		font-weight: bold;
		padding: 8px;
		margin: 2px 0;
		border-radius: 4px;
	}

	.log-entry.tap-perfect {
		background-color: #e3f2fd;
		border-left: 3px solid #2196F3;
		color: #2196F3;
	}

	.log-entry.tap-great {
		background-color: #e8f5e9;
		border-left: 3px solid #4caf50;
		color: #4caf50;
	}

	.log-entry.tap-good {
		background-color: #fff3e0;
		border-left: 3px solid #ff9800;
		color: #ff9800;
	}

	.log-entry.tap-miss {
		background-color: #ffebee;
		border-left: 3px solid #f44336;
		color: #f44336;
	}

	.time {
		color: #666;
	}

	.content {
		color: #333;
	}

	@media (max-width: 600px) {
		.container {
			margin: 0;
			border-radius: 0;
			min-height: 100vh;
		}

		.controls {
			flex-direction: column;
		}

		.control-item {
			width: 100%;
			justify-content: space-between;
			padding: 0 20px;
		}

		.time-header,
		.time-entry {
			grid-template-columns: 2fr 2fr 3fr;
			font-size: 12px;
		}
	}
</style>
